#include "Components/ComponentMiner.h"
#include "Components/ComponentCreatureModel.h"
#include "Components/ComponentInventory.h"
#include "Components/ComponentCreativeInventory.h"
#include "Components/ComponentCreature.h"
#include "Components/ComponentBody.h"
#include "Components/ComponentPlayer.h"
#include "Components/ComponentLevel.h"

#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemBodies.h"

#include "Player/PlayerData.h"
#include "Managers/BlocksManager.h"
#include "Game/BodyRaycastResult.hpp"
#include "Game/TerrainRaycastResult.hpp"
#include "Game/MovingBlocksRaycastResult.hpp"

#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/BlockBehaviors/SubsystemBlockBehavior.hpp"
#include "Subsystems/BlockBehaviors/SubsystemBlockBehaviors.h"
#include "Log.h"
#include "GameSingleton.h"
#include "GameRegistry.hpp"
#include "Expand/EventManager.h"
#include <float.h>
#include <cmath>


namespace PocketSurvival
{
    bool ComponentMiner::dig(const TerrainRaycastResult &raycastResult, bool isEnd)
    {
        SubsystemTimeOfDay *timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();

        bool result = false;
        CellFace cellFace = raycastResult.cellFace;
        int32_t cellValue = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>()->getCellValue(cellFace.point);

        int32_t num = Terrain::ExtractContents(cellValue);
        Block *block = GameSingleton::blocksManager->blocks[num];
        int32_t activeBlockValue = getActiveBlockValue();
        int32_t num2 = Terrain::ExtractContents(activeBlockValue);
        Block *block2 = GameSingleton::blocksManager->blocks[num2];

        if (digCellFace == nullptr || digCellFace->point.posX != cellFace.point.posX || digCellFace->point.posY != cellFace.point.posY || digCellFace->point.posZ != cellFace.point.posZ)
        {
            digStartTime = timeOfDay->getGameTime();
            digCellFace = std::make_shared<CellFace>(cellFace);
            digFaceChange = true;
        }
        float num3 = calculateDigTime(cellValue, num2);
        digProgress = ((num3 > 0.0f) ? MathUtils::Saturate((timeOfDay->getGameTime() - digStartTime) / num3) : 1.0f);
        if (canUseTool(getActiveBlockValue()) == false)
        {
            digProgress = 0.0f;
        }
        if (isEnd)
        {
            digProgress += 0.11f;
            digCellFace = nullptr;

            if (digProgress >= 1.0f)
            {
                SubsystemTerrain *subsystemTerrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();

                BlockPlacementData digValue = block->getDigValue(this, cellValue, activeBlockValue, raycastResult);

                // 玩家破坏方块捕捉
                int32_t nowValue = subsystemTerrain->getCellValue(digValue.cellFace.point);
                if (false == GameSingleton::eventManager->playerEvent->playerBreakBlock(componentPlayer, digValue.cellFace.point, nowValue, block2->ToolLevel))
                {
                    return false;
                }

                //
                //
                //

                subsystemTerrain->destroyCell(block2->ToolLevel, digValue.cellFace.point.posX, digValue.cellFace.point.posY, digValue.cellFace.point.posZ, digValue.value, false, this);
                damageActiveTool(1);

                // 记录玩家破坏方块的数量...暂时不打算实现...
                //
                //
                //

                result = true;
            }
        }
        return result;
    }

    bool ComponentMiner::place(const TerrainRaycastResult &raycastResult)
    {
        if (getActiveBlockCount() > 0 && place(raycastResult, getActiveBlockValue()))
        {
            if (inventory != nullptr)
            {
                inventory->removeSlotItems(inventory->getActiveSlotIndex(), 1);
            }
            return true;
        }
        return false;
    }
    bool ComponentMiner::place(const TerrainRaycastResult &raycastResult, int32_t value)
    {
        int32_t content = Terrain::ExtractContents(value);
        Block *block = GameSingleton::blocksManager->blocks[content];
        if (block->IsPlaceable)
        {
            BlockPlacementData placementData = block->getPlacementValue(this, value, raycastResult);
            if (placementData.value != 0)
            {
                Point3 point = CellFace::FaceToPoint3(placementData.cellFace.face);
                int32_t num2 = placementData.cellFace.point.posX + point.posX;
                int32_t num3 = placementData.cellFace.point.posY + point.posY;
                int32_t num4 = placementData.cellFace.point.posZ + point.posZ;
                if (num3 > 0 && num3 < 255)
                {
                    bool flag = false;
                    if (block->IsCollidable)
                    {
                        BoundingBox boundingBox = componentPlayer->componentBody->getBoundingBox();
                        boundingBox.Min += 0.2f;
                        boundingBox.Max -= 0.2f;
                        const std::vector<BoundingBox> &customCollisionBoxes = block->getCustomCollisionBoxes(placementData.value);
                        for (int32_t i = 0; i < customCollisionBoxes.size(); i++)
                        {
                            BoundingBox box = customCollisionBoxes[i];
                            box.Min += Vector3(num2, num3, num4);
                            box.Max += Vector3(num2, num3, num4);
                            if (boundingBox.intersection(box))
                            {
                                flag = true;
                                break;
                            }
                        }
                    }
                    if (!flag)
                    {
                        SubsystemTerrain *subsystemTerrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
                        Point3 blockPoint = Point3(num2, num3, num4);
                        int32_t nowValue = subsystemTerrain->getCellValue(blockPoint);
                        // 相同的方块不能替换
                        if (nowValue == placementData.value)
                        {
                            return false;
                        }

                        // 玩家放置方块行为捕捉
                        if (GameSingleton::eventManager->playerEvent->playerPlaceBlock(componentPlayer, blockPoint, placementData.value) == false)
                        {
                            return false;
                        }

                        std::vector<SubsystemBlockBehavior *> &blockBehaviors = GameSingleton::singletonPtr->getSingleton<SubsystemBlockBehaviors>()->getBlockBehaviors(Terrain::ExtractContents(placementData.value));
                        for (auto behaviorIt = blockBehaviors.begin(); behaviorIt != blockBehaviors.end(); ++behaviorIt)
                        {
                            (*behaviorIt)->onBlockPlaced(this, num2, num3, num4, placementData, value);
                        }
                        subsystemTerrain->destroyCell(0, num2, num3, num4, placementData.value, false, this);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    bool ComponentMiner::use(const Ray3 &ray)
    {
        int32_t num = Terrain::ExtractContents(getActiveBlockValue());
        Block *block = GameSingleton::blocksManager->blocks[num];
        if (canUseTool(getActiveBlockValue()) == false)
        {
            return false;
        }
        if (GameSingleton::eventManager->playerEvent->onUse(this, ray) == false)
        {
            return false;
        }
        std::vector<SubsystemBlockBehavior *> &blockBehaviors = GameSingleton::singletonPtr->getSingleton<SubsystemBlockBehaviors>()->getBlockBehaviors(num);
        for (auto &behavior : blockBehaviors)
        {
            if (behavior->onUse(ray, this) == true)
            {
                return true;
            }
        }
        return false;
    }

    bool ComponentMiner::interact(const TerrainRaycastResult &raycastResult)
    {
        int32_t cellContents = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>()->getCellContents(raycastResult.cellFace.point);
        std::vector<SubsystemBlockBehavior *> &blockBehaviors = GameSingleton::singletonPtr->getSingleton<SubsystemBlockBehaviors>()->getBlockBehaviors(cellContents);
        for (auto &behavior : blockBehaviors)
        {
            if (behavior->onInteract(raycastResult, this) == true)
            {
                return true;
            }
        }
        return false;
    }

    bool ComponentMiner::aim(const Ray3 &aim, AimState state)
    {
        int32_t num = Terrain::ExtractContents(getActiveBlockValue());
        Block *block = GameSingleton::blocksManager->blocks[num];
        if (block->IsAimable)
        {
            if (canUseTool(getActiveBlockValue()) == false)
            {
                return false;
            }
            if (GameSingleton::eventManager->playerEvent->onAim(this, aim, state) == false)
            {
                return false;
            }
            std::vector<SubsystemBlockBehavior *> &blockBehaviors = GameSingleton::singletonPtr->getSingleton<SubsystemBlockBehaviors>()->getBlockBehaviors(num);
            for (auto &behavior : blockBehaviors)
            {
                if (behavior->onAim(aim, this, state) == true)
                {
                    return true;
                }
            }
        }
        return false;
    }

    RaycastData ComponentMiner::raycast(const Ray3 &ray, RaycastMode mode)
    {
        RaycastData raycastData = {nullptr};
        float reach = (componentPlayer->playerData->gameMode == GameMode::Creative ? 32.0f : 5.0f);
        Vector3 creaturePosition = componentCreature->componentCreatureModel->getEyePosition();
        Vector3 start = ray.Position;
        Vector3 direction = Vector3::Normalize(ray.Direction);
        Vector3 end = ray.Position + direction * 15.0f;
        Point3 startCell = Terrain::ToCell(start);
        std::shared_ptr<BodyRaycastResult> bodyRaycastResult = GameSingleton::singletonPtr->getSingleton<SubsystemBodies>()->raycast(
            start,
            end,
            0.35f,
            [&](ComponentBody *body, float distance) -> bool
            {
                return (Vector3::DistanceSquared(start + distance * direction, creaturePosition) <= reach * reach &&
                        body->entity != this->entity &&
                        Vector3::Dot(Vector3::Normalize(body->getBoundingBox().center() - start), direction) > 0.7f);
            });
        std::shared_ptr<MovingBlocksRaycastResult> movingBlocksRaycastResult = nullptr;
        std::shared_ptr<TerrainRaycastResult> terrainRaycastResult = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>()->raycast(
            start,
            end,
            true,
            true,
            [&](int32_t value, float distance) -> bool
            {
                if (Vector3::DistanceSquared(start + distance * direction, creaturePosition) <= reach * reach)
                {
                    Block *block = GameSingleton::blocksManager->blocks[Terrain::ExtractContents(value)];
                    if (distance == 0.0f && Vector3::Dot(direction, Vector3(startCell) + 0.5f - start) < 0.0f)
                    {
                        return false;
                    }
                    if (mode == RaycastMode::Digging)
                    {
                        return block->IsDiggingTransparent == false;
                    }
                    if (mode == RaycastMode::Interaction)
                    {
                        if (block->IsPlacementTransparent)
                        {
                            return block->isInteractive(value);
                        }
                        return true;
                    }
                    if (mode == RaycastMode::Gathering)
                    {
                        return block->IsGatherable;
                    }
                }
                return false;
            });
        float num = bodyRaycastResult != nullptr ? bodyRaycastResult->distance : FLT_MAX;
        float num2 = movingBlocksRaycastResult != nullptr ? movingBlocksRaycastResult->distance : FLT_MAX;
        float num3 = terrainRaycastResult != nullptr ? terrainRaycastResult->distance : FLT_MAX;
        if (num < num2 && num < num3)
        {
            raycastData.bodyRaycastResult = bodyRaycastResult;
            return raycastData;
        }
        if (num2 < num && num2 < num3)
        {
            raycastData.movingBlocksRaycastResult = movingBlocksRaycastResult;
            return raycastData;
        }
        if (num3 < num && num3 < num2)
        {
            raycastData.terrainRaycastResult = terrainRaycastResult;
            return raycastData;
        }
        raycastData.ray3 = std::make_shared<Ray3>(start, direction);
        return raycastData;
    }

    void ComponentMiner::removeActiveTool(int32_t removeCount)
    {
        if (inventory != nullptr)
        {
            inventory->removeSlotItems(inventory->getActiveSlotIndex(), removeCount);
        }
    }
    void ComponentMiner::damageActiveTool(int32_t damageCount)
    {
        if (componentPlayer->playerData->gameMode == GameMode::Creative || inventory == nullptr)
        {
            return;
        }
        int32_t num = GameSingleton::blocksManager->damageItem(getActiveBlockValue(), damageCount);
        if (num != 0)
        {
            int32_t slotCount = inventory->getSlotCount(inventory->getActiveSlotIndex());
            inventory->removeNetSlotItems(inventory->getActiveSlotIndex(), slotCount);
            if (inventory->getSlotCount(inventory->getActiveSlotIndex()) == 0)
            {
                inventory->addSlotItems(inventory->getActiveSlotIndex(), num, slotCount);
            }
        }
        else
        {
            inventory->removeSlotItems(inventory->getActiveSlotIndex(), 1);
        }
    }

    void ComponentMiner::update(float dt)
    {
        float num = componentPlayer->playerData->gameMode == GameMode::Creative ? 0.5f : 4.0f;
        lastPokingPhase = pokingPhase;
        if (digCellFace != nullptr || pokingPhase > 0.0f)
        {
            pokingPhase += num * dt;
            if (pokingPhase > 1.0f)
            {
                if (digCellFace != nullptr)
                {
                    pokingPhase = MathUtils::Remainder(pokingPhase, 1.0f);
                }
                else
                {
                    pokingPhase = 0.0f;
                }
            }
        }
    }

    float ComponentMiner::calculateDigTime(int32_t digValue, int32_t toolContents)
    {
        Block *block  = GameSingleton::blocksManager->blocks[toolContents];
        Block *block2 = GameSingleton::blocksManager->blocks[Terrain::ExtractContents(digValue)];
        BlockDigMethod digMethod = block2->getBlockDigMethod(digValue);
        float digResilience = block2->getDigResilience(digValue);
        SubsystemGameInfo *gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
        if (componentPlayer->playerData->gameMode == GameMode::Creative)
        {
            if (digResilience < INFINITY)
            {
                return 0.0f;
            }
            return INFINITY;
        }
        if (componentPlayer->playerData->gameMode == GameMode::Adventure)
        {
            float num = 0.0f;
            if (digMethod == BlockDigMethod::Shovel && block->ShovelPower >= 2.0f)
            {
                num = block->ShovelPower;
            }
            else if (digMethod == BlockDigMethod::Quarry && block->QuarryPower >= 2.0f)
            {
                num = block->QuarryPower;
            }
            else if (digMethod == BlockDigMethod::Hack && block->HackPower >= 2.0f)
            {
                num = block->HackPower;
            }
            num *= componentPlayer->componentLevel->getStrengthFactor();
            if (num > 0.0f)
            {
                return MathUtils::Max<float>(digResilience / num, 0.0f);
            }
            return INFINITY;
        }
        float num2 = 0.0f;
        if (digMethod == BlockDigMethod::Shovel)
        {
            num2 = block->ShovelPower;
        }
        else if (digMethod == BlockDigMethod::Quarry)
        {
            num2 = block->QuarryPower;
        }
        else if (digMethod == BlockDigMethod::Hack)
        {
            num2 = block->HackPower;
        }
        num2 *= componentPlayer->componentLevel->getStrengthFactor();
        if (num2 > 0.0f)
        {
            return MathUtils::Max<float>(digResilience / num2, 0.0f);
        }
        return INFINITY;
    }

    bool ComponentMiner::canUseTool(int32_t toolValue)
    {
        SubsystemGameInfo *gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
        if (componentPlayer->playerData->gameMode != GameMode::Creative && gameInfo->areAdventureSurvivalMechanicsEnabled == true)
        {
            Block *block = GameSingleton::blocksManager->blocks[Terrain::ExtractContents(toolValue)];
            if (componentPlayer->playerData->level < (float)block->PlayerLevelRequired)
            {
                return false;
            }
        }
        return true;
    }

    static const std::string componentName = "Miner";
    const std::string &ComponentMiner::getName() const
    {
        return componentName;
    }
    const std::string &ComponentMiner::GetName()
    {
        return componentName;
    }
    void ComponentMiner::load(const nlohmann::json &jsonData)
    {
        componentPlayer = GameSingleton::gameRegistry->get<ComponentPlayer *>(entity);
        componentCreature = GameSingleton::gameRegistry->get<ComponentCreature *>(entity);
        PlayerData *playerData = GameSingleton::gameRegistry->get<PlayerData *>(entity);

        if (playerData->gameMode == GameMode::Creative)
        {
            ComponentCreativeInventory **creativeInventoryPtr = GameSingleton::gameRegistry->try_get<ComponentCreativeInventory *>(entity);
            if (creativeInventoryPtr != nullptr)
            {
                inventory = *creativeInventoryPtr;
            }
        }
        else
        {
            ComponentInventory **inventoryPtr = GameSingleton::gameRegistry->try_get<ComponentInventory *>(entity);
            if (inventoryPtr != nullptr)
            {
                inventory = *inventoryPtr;
            }
        }
    }
    void ComponentMiner::save(nlohmann::json &jsonData)
    {
    }
    void ComponentMiner::saveToMsgJson(nlohmann::json &jsonData)
    {
    }

}